import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;

import org.json.simple.JSONArray;

import com.dropbox.client.Authenticator;
import com.dropbox.client.DropboxClient;
import com.dropbox.client.DropboxException;
import com.dropbox.client.TrustedAuthenticator;

/**
 * every 10 seconds, connect to Dropbox server,
 * retrieve the metadata of the given path,
 * calculate list of files that have been deleted,
 * list of files that have been modified,
 * and list of files that have been newly created
 * 
 * @author g0900398
 *
 */
public class UpdateChecker extends Thread{

	//private DropboxClient dbClient; //removed by u099048h
	private String root;	// "sandbox"
	private String path;	// of the form "/testpath"
	private String backUpPath;
	private int checkPeriod;	// for example, 10000, means 10 seconds
	
	//added u099048h
	private SecurityHandler securityHandler;
	
	SmartManager sm;
	/*
	 * when starting a new UpdateChecker thread, Stypepad should 
	 * pass me the original copy of the metadata of that path,
	 * such that I can check whether updates have been made or not,
	 */
	@SuppressWarnings("unchecked")
	private Map metadata; 
	
	ArrayList<String> deletedFiles;
	ArrayList<String> modifiedFiles;
	
	@SuppressWarnings("unchecked")
	public UpdateChecker(SecurityHandler secHandler, String path, Map map){
		//changed u099048h
		//this.dbClient = client;
		this.securityHandler = secHandler;
		
		this.root = "sandbox";
		this.path = path;
		this.checkPeriod = 10000;
		this.metadata = map;
	}
	
	@SuppressWarnings("unchecked")
	public UpdateChecker(SecurityHandler secHandler, String path, int period, Map map){
		//changed u099048h
		//this.dbClient = client;
		this.securityHandler = secHandler;
		
		this.root = "sandbox";
		this.path = path;
		this.checkPeriod = period;
		this.metadata = map;
	}
	
	@SuppressWarnings("unchecked")
	public UpdateChecker(SecurityHandler secHandler, String root, String path, int period, Map map, SmartManager sm, String backUpPath){
		//changed u099048h
		//this.dbClient = client;
		this.securityHandler = secHandler;
				
		this.root = root;
		this.path = path;
		this.checkPeriod = period;
		this.metadata = map;
		this.sm = sm;
		this.backUpPath = backUpPath;
	}
	
	public void run(){

		if(metadata == null){
			//changed u099048h
			//metadata = dbClient.metadata(root, path, 100, null, true, false, null);
			metadata = securityHandler.metadata(null, false, 100, null, true, false, null);
			
			Statistics.metadataRequest ++;
			System.out.println("UC##%% Add 1 to metadata request.");
		}
		
		if(metadata == null){
			System.out.println("Cannot get the initial meta data!");
			return;
		}
		
		String hash = (String)metadata.get("hash");
		if(hash == null){
			System.out.println("hash value for a directory should always be valid!");
			return;
		}
		
		while(true){
			try {
				Thread.sleep((long)checkPeriod);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			new CheckOnce().start();
		}
		
	}

	// @author U097023U
	public void recover ()
	{
		//changed u099048h
		//Map mainFileMeta = dbClient.metadata (root, path, 100, null, true, false, null);
		ArrayList<String> mainFileList = new ArrayList<String>();
		do{
			Map mainFileMeta = securityHandler.metadata (null, false, 100, null, true, false, null);
			mainFileList = DropboxCommunicator.getFilenameListFromMeta (mainFileMeta);
		}while (mainFileList == null);
		
		//changed u099048h
		//Map backUpFolderMeta = dbClient.metadata (root, backUpPath, 100, null, true, false, null);
		ArrayList<String> backUpFolderList = new ArrayList<String>();
		
		Map backUpFolderMeta = securityHandler.metadata (null, true, 100, null, true, false, null);
		backUpFolderList = DropboxCommunicator.getFilenameListFromMeta (backUpFolderMeta);
		
		if(backUpFolderList != null){
			for (int i = 0; i < backUpFolderList.size(); i++)
			{
				//changed u099048h
				//Map backUpFileMeta = dbClient.metadata (root, backUpPath + "/" + backUpFolderList.get(i), 100, null, true, false, null);
				Map backUpFileMeta = securityHandler.metadata (backUpFolderList.get(i), true, 100, null, true, false, null);
	
				ArrayList<String> backUpFileList = DropboxCommunicator.getFilenameListFromMeta (backUpFileMeta);
				for (int j = 0; j < backUpFileList.size (); j++)
				{
					String bkMainNameSeg = getMainFilename (backUpFileList.get(j));
					
					// Remove all consistent files.
					for (int k = 0; k < mainFileList.size(); k++)
					{
						String mainNameSeg = getMainFilename (mainFileList.get(k));
						if (bkMainNameSeg.equals(mainNameSeg))
						{
							DropboxCommunicator.deleteSingleFile (mainFileList.get(k));
						}
					}
					
					// Move back up files back to their original folder.
					DropboxCommunicator.moveFile ("Backup" + "/" + backUpFolderList.get(i), backUpFileList.get(j), backUpFileList.get(j));
				}
				
				DropboxCommunicator.deleteFolder (backUpPath + "/" + backUpFolderList.get(i));
			}
		}
	}
	
	public class CheckOnce extends Thread{
		
		@SuppressWarnings("unchecked")
		public void run ()
		{
			// here.
			recover ();
			try 
			{
				//changed u099048h				
				//Map newmeta = dbClient.metadata (root, path, 100, (String) metadata.get ("hash"), true, false, null);
				Map newmeta = securityHandler.metadata (null, false, 100, (String) metadata.get ("hash"), true, false, null);
				
				Statistics.metadataRequest ++;
				System.out.println("UC##*MetadataRequest Add 1");
				
				if(newmeta.get("ERROR") != null){
					if(newmeta.get("ERROR").equals("HTTP/1.1 304 Not Modified")){
						//System.out.println("Files under path " + path + "not modified.");
					}
					else
					{
						System.out.println(newmeta.get("ERROR"));
					}
				}
				else{
					//files under that path have been modified
					ArrayList<DropboxFile> oldFileList = getFilelistFromMetadata(metadata);
					ArrayList<DropboxFile> newFileList = getFilelistFromMetadata(newmeta);
					getChangedFileList(oldFileList, newFileList);
					metadata = newmeta;
					
					// need to check whether the files are modified by the user or not
					// if modified by the user, then no need any action
					if(!deletedFiles.isEmpty()){
						
						SwingUtilities.invokeLater(new Runnable(){public void run(){
						    //Update the model here
							sm.updateNotification(null, deletedFiles, 0);
						}});

					}
					
					if(!modifiedFiles.isEmpty())
						//changed u099048h
						//new FileLoader(dbClient, root, path, modifiedFiles, sm).start();
						new FileLoader(securityHandler, root, path, modifiedFiles, sm).start();
				}
						
			}
			//@author U097023U
			catch (Exception e) {
				
				JOptionPane.showMessageDialog (sm.editor.getFrame (), 
						   "Please check your connection...",
						   "Connection lost!",
						   JOptionPane.ERROR_MESSAGE);
				
				
				//e.printStackTrace();
			}
		}
		
	}
	
	public class DropboxFile{
		String fileName;
		String modifiedDate;
		boolean isIn;
		
		public DropboxFile(String name, String date){
			this.fileName = name;
			this.modifiedDate = date;
			this.isIn = false;
		}
	}
	
	public class NamedDocument{
		String fileName;
		Document doc;
		
		public NamedDocument(String file, Document doc){
			this.fileName = file;
			this.doc = doc;
		}
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<DropboxFile> getFilelistFromMetadata(Map meta){
		
		ArrayList<DropboxFile> dbFile = new ArrayList<DropboxFile>();
		Object json_contents = meta.get("contents");
        if (json_contents != null && json_contents instanceof JSONArray) {
            Object entry;
            Iterator it = ((JSONArray)json_contents).iterator();
            while (it.hasNext()) {
                entry = it.next();
                if (entry instanceof Map) {
                	String path1 = (String)((Map) entry).get("path");
                	String[] dirs = path1.split("/"); // last element of dirs should be the file name
                	
                	if(!path1.equals(path + "/" + "server_log_file_N")){
                		if(!path1.equals(path + "/" + "server_log_file_L"))
                       		dbFile.add(new DropboxFile(dirs[dirs.length - 1], (String)((Map) entry).get("modified")));        
                	}
                }
            }
            return dbFile;
        }  
        return null;
	}
	
	/**
	 * get the first two parts of a file's name, called its main file name
	 * note that each file name is of the following form:
	 * 		para_paraNum_lineNum_L/N
	 * where paraNum is a float number representing which part this file belongs to,
	 * lineNum is a integer indicates how many lines this file contains,
	 * and L/N indicates whether this file has been locked (L) or not (N)
	 */
	public String getMainFilename(String fullFileName){
		String[] parts = fullFileName.split("_");
		if(parts.length == 4)
			return new String(parts[0]+"_"+parts[1]);
		else
			return null;
	}
	
	public ArrayList<String> getMainFilenameList(ArrayList<DropboxFile> fileList){
		ArrayList<String> mainFilenameList = new ArrayList<String>();
		if(fileList != null)
			for(int i = 0; i < fileList.size(); i++){
				mainFilenameList.add(getMainFilename(fileList.get(i).fileName));
			}
		return mainFilenameList;
	}
	

	public void getChangedFileList(ArrayList<DropboxFile> oldFileList, ArrayList<DropboxFile> newFileList){
		ArrayList<String> deletedFilenameList = new ArrayList<String>();
		ArrayList<String> modifiedFilenameList = new ArrayList<String>();
		
		ArrayList<String> oldMainFilename = getMainFilenameList(oldFileList);
		ArrayList<String> newMainFilename = getMainFilenameList(newFileList);
		
		//String username = Stylepad.getUserName();
		
		for(int i = 0; i < newMainFilename.size(); i++){
			int j = oldMainFilename.indexOf((String)newMainFilename.get(i));
			if(j >= 0){
				oldFileList.get(j).isIn = true;
				newFileList.get(i).isIn = true;
				if(!newFileList.get(i).modifiedDate.equals(oldFileList.get(j).modifiedDate))
					//if(!(username.equals(getUsernameFormFilename(oldFileList.get(j).fileName)) ||
						//username.equals(getUsernameFormFilename(newFileList.get(i).fileName))))
					if(SmartManager.ignoredFilelist.indexOf(newMainFilename.get(i)) < 0)
					{
						modifiedFilenameList.add(newFileList.get(i).fileName);
					}
					else
					{
						SmartManager.ignoredFilelist.remove(newMainFilename.get(i));
					}
			}
			else
				//if(!username.equals(getUsernameFormFilename(newFileList.get(i).fileName)))
					//modifiedFilenameList.add(newFileList.get(i).fileName);
				if(SmartManager.ignoredFilelist.indexOf(newMainFilename.get(i)) < 0)
				{
					modifiedFilenameList.add(newFileList.get(i).fileName);
				}
				else
				{
					SmartManager.ignoredFilelist.remove(newMainFilename.get(i));
				}
					

		}
		for(int i = 0; i < oldFileList.size(); i++)
			if(!oldFileList.get(i).isIn){
				if(SmartManager.ignoredFilelist.indexOf(oldMainFilename.get(i)) < 0)
					deletedFilenameList.add(oldFileList.get(i).fileName);
				else
					SmartManager.ignoredFilelist.remove(oldMainFilename.get(i));
			}

		for(int i = 0; i < deletedFilenameList.size(); i++){
			if(Stylepad.getUserName().equals(SDAction.getUsernameFromFile(deletedFilenameList.get(i))))
				deletedFilenameList.remove(i);
		}
		
		for(int i = 0; i < modifiedFilenameList.size(); i++){
			if(Stylepad.getUserName().equals(SDAction.getUsernameFromFile(modifiedFilenameList.get(i))))
				modifiedFilenameList.remove(i);
		}
		
		deletedFiles = deletedFilenameList;
		for(int i = 0; i < deletedFiles.size(); i++){
			System.out.println("deleted files: ");
			System.out.println(deletedFiles.get(i));		
		}
		modifiedFiles = modifiedFilenameList;
		for(int i = 0; i < modifiedFiles.size(); i++){
			System.out.println("modified files: ");
			System.out.println(modifiedFiles.get(i));
		}
	}
	
	public String getUsernameFormFilename(String filename){
		if(filename == null)
			return null;
		String[] name = filename.split("_");
		if(name.length != 4)
			return null;
		if(name[3].length() <= 1)
			return null;
		
		return name[3].substring(1, name[3].length() - 2);	// (username)L
	}
			
	
	/**
	 * @param args
	 * @throws DropboxException 
	 */
	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws DropboxException {
		// TODO Auto-generated method stub

		Map config = null;
		Authenticator auth = null;
		
		try {
			config = TrustedAuthenticator.loadConfig("config/trusted_testing.json");
			TrustedAuthenticator trusted_auth = new TrustedAuthenticator(config);
			String username = (String)config.get("testing_user");
			assert username != null : "You failed to set the testing_user for trusted access.";
			String password = (String)config.get("testing_password");
			assert password != null : "You failed to set the testing_password for trusted access.";
			trusted_auth.retrieveTrustedAccessToken(username, password);
			auth = trusted_auth;
		} catch (Exception e) {
			e.printStackTrace();
			assert false : "Total failure trying to access the trusted authenticator." + e;
		}
      
		assert auth != null : "Auth didn't get configured.";
		DropboxClient client = new DropboxClient(config, auth);
		System.out.println("connected to Dropbox server...");
		Map meta = client.metadata("sandbox", "/test", 100, null, true, false, null);
		System.out.println("retrieved initial metadata...");
		printMetadata(meta);
		//SmartManager.ignoredFilelist.add("para_2");
		//SmartManager.ignoredFilelist.add("para_3");
		//if(meta != null)
			//new UpdateChecker(client, "sandbox", "/test", 10000, meta).start();
	}
	
	@SuppressWarnings("unchecked")
	public static void printMetadata(Map map){
    	//Map meta = (Map) map.get("body");
    	Map meta = map;
    	if(meta != null && meta instanceof Map){
    		System.out.println("bytes: " + meta.get("bytes"));
    		System.out.println("hash: " + meta.get("hash"));
    		System.out.println("icon: " + meta.get("icon"));
    		System.out.println("is_dir: " + meta.get("is_dir"));
    		System.out.println("modified: " + meta.get("modified"));
    		System.out.println("path: " + meta.get("path"));
    		System.out.println("root: " + meta.get("root"));
    		System.out.println("size: " + meta.get("size"));
    		System.out.println("mime_type: " + meta.get("mime_type"));
    		System.out.println("revision: " + meta.get("revision"));
    		System.out.println("thumb_exists: " + meta.get("thumb_exists"));
    		Object json_contents = meta.get("contents");
            if (json_contents != null && json_contents instanceof JSONArray) {
                Object entry;
                Iterator it = ((JSONArray)json_contents).iterator();
                while (it.hasNext()) {
                    entry = it.next();
                    if (entry instanceof Map) {
                        printMetadata((Map) entry);        
                    }
                }
            }      
    	}
    	else
    		System.out.println("cannot get body");
    	
    }

}
